use crate::db::dao::traffic_steering_rule::TrafficSteeringRuleDao;
use crate::db::{dao, po};
use crate::dto;
use crate::service::Error;
use std::sync::Arc;
use wanmesh_common::error::ResponseError;

#[async_trait::async_trait]
pub trait TrafficSteeringRuleService: Sync + Send {
    async fn add(
        &self,
        params: &dto::core::traffic_steering_rule::AddParams,
    ) -> Result<dto::core::traffic_steering_rule::AddResponseData, Error>;

    async fn list(
        &self,
        filter: &dto::core::traffic_steering_rule::Filter,
        offset: u64,
        limit: u64,
    ) -> Result<Vec<dto::core::traffic_steering_rule::TrafficSteeringRuleItem>, Error>;

    async fn get(
        &self,
        traffic_steering_rule_id: &str,
    ) -> Result<dto::core::traffic_steering_rule::TrafficSteeringRuleItem, Error>;

    async fn try_get(
        &self,
        traffic_steering_rule_id: &str,
    ) -> Result<Option<dto::core::traffic_steering_rule::TrafficSteeringRuleItem>, Error>;

    async fn update(
        &self,
        traffic_steering_rule_id: &str,
        params: &dto::core::traffic_steering_rule::UpdateParams,
    ) -> Result<(), Error>;

    async fn delete(&self, traffic_steering_rule_id: &str) -> Result<(), Error>;

    async fn delete_by_filter(
        &self,
        filter: &dto::core::traffic_steering_rule::Filter,
    ) -> Result<(), Error>;

    async fn count(&self, filter: &dto::core::traffic_steering_rule::Filter) -> Result<u64, Error>;
}

pub struct DefaultTrafficSteeringRuleService {
    traffic_steering_rule_dao: Arc<dyn TrafficSteeringRuleDao>,
}

impl DefaultTrafficSteeringRuleService {
    pub fn new(traffic_steering_rule_dao: Arc<dyn TrafficSteeringRuleDao>) -> Self {
        DefaultTrafficSteeringRuleService {
            traffic_steering_rule_dao,
        }
    }
}

#[async_trait::async_trait]
impl TrafficSteeringRuleService for DefaultTrafficSteeringRuleService {
    async fn add(
        &self,
        params: &dto::core::traffic_steering_rule::AddParams,
    ) -> Result<dto::core::traffic_steering_rule::AddResponseData, Error> {
        let traffic_steering_rule_id = wanmesh_common::util::new_id()?;
        self.traffic_steering_rule_dao
            .insert(po::traffic_steering_rule::ActiveModel {
                id: Default::default(),
                traffic_steering_rule_id: po::ActiveValue::Set(traffic_steering_rule_id.clone()),
                endpoint_id: po::ActiveValue::Set(params.endpoint_id.clone()),
                te_policy_group_id: po::ActiveValue::Set(params.te_policy_group_id.clone()),
                network_acl_id: po::ActiveValue::Set(params.network_acl_id.clone()),
                description: po::ActiveValue::Set(params.description.clone()),
                create_timestamp_millis: Default::default(),
                update_timestamp_millis: Default::default(),
            })
            .await?;

        Ok(dto::core::traffic_steering_rule::AddResponseData {
            traffic_steering_rule_id,
        })
    }

    async fn list(
        &self,
        filter: &dto::core::traffic_steering_rule::Filter,
        offset: u64,
        limit: u64,
    ) -> Result<Vec<dto::core::traffic_steering_rule::TrafficSteeringRuleItem>, Error> {
        let po_list = self
            .traffic_steering_rule_dao
            .find(dao::FindParams {
                filter: filter.into(),
                offset: Some(offset),
                limit: Some(limit),
                ..Default::default()
            })
            .await?;

        let list = po_list.into_iter().map(|v| v.into()).collect();

        Ok(list)
    }

    async fn get(
        &self,
        traffic_steering_rule_id: &str,
    ) -> Result<dto::core::traffic_steering_rule::TrafficSteeringRuleItem, Error> {
        if let Some(rule) = self.try_get(traffic_steering_rule_id).await? {
            return Ok(rule);
        }

        Err(ResponseError::traffic_steering_rule_not_found(traffic_steering_rule_id).into())
    }

    async fn try_get(
        &self,
        traffic_steering_rule_id: &str,
    ) -> Result<Option<dto::core::traffic_steering_rule::TrafficSteeringRuleItem>, Error> {
        let filter = dao::traffic_steering_rule::Filter {
            traffic_steering_rule_id: Some(traffic_steering_rule_id.to_string()),
            ..Default::default()
        };

        if let Some(m) = self.traffic_steering_rule_dao.try_get(filter).await? {
            return Ok(Some(m.into()));
        }

        Ok(None)
    }

    async fn update(
        &self,
        traffic_steering_rule_id: &str,
        params: &dto::core::traffic_steering_rule::UpdateParams,
    ) -> Result<(), Error> {
        if params == &Default::default() {
            return Ok(());
        }

        let filter = dao::traffic_steering_rule::Filter {
            traffic_steering_rule_id: Some(traffic_steering_rule_id.to_string()),
            ..Default::default()
        };

        self.traffic_steering_rule_dao
            .update(filter, params.into())
            .await?;

        Ok(())
    }

    async fn delete(&self, traffic_steering_rule_id: &str) -> Result<(), Error> {
        let filter = dao::traffic_steering_rule::Filter {
            traffic_steering_rule_id: Some(traffic_steering_rule_id.to_string()),
            ..Default::default()
        };

        self.traffic_steering_rule_dao.delete(filter).await?;

        Ok(())
    }

    async fn delete_by_filter(
        &self,
        filter: &dto::core::traffic_steering_rule::Filter,
    ) -> Result<(), Error> {
        if filter == &Default::default() {
            return Err(ResponseError::filter_can_not_be_empty().into());
        }

        self.traffic_steering_rule_dao.delete(filter.into()).await?;

        Ok(())
    }

    async fn count(&self, filter: &dto::core::traffic_steering_rule::Filter) -> Result<u64, Error> {
        let count = self.traffic_steering_rule_dao.count(filter.into()).await?;
        Ok(count)
    }
}

impl From<&dto::core::traffic_steering_rule::Filter> for dao::traffic_steering_rule::Filter {
    fn from(value: &dto::core::traffic_steering_rule::Filter) -> Self {
        Self {
            traffic_steering_rule_id: None,
            endpoint_id: value.endpoint_id.clone(),
            te_policy_group_id: value.te_policy_group_id.clone(),
            network_acl_id: value.network_acl_id.clone(),
        }
    }
}

impl From<dto::core::traffic_steering_rule::Filter> for dao::traffic_steering_rule::Filter {
    fn from(value: dto::core::traffic_steering_rule::Filter) -> Self {
        Self::from(&value)
    }
}

impl From<&dto::core::traffic_steering_rule::UpdateParams>
    for dao::traffic_steering_rule::UpdateParams
{
    fn from(value: &dto::core::traffic_steering_rule::UpdateParams) -> Self {
        Self {
            description: po::option_to_active_value(value.description.clone()),
        }
    }
}

impl From<dto::core::traffic_steering_rule::UpdateParams>
    for dao::traffic_steering_rule::UpdateParams
{
    fn from(value: dto::core::traffic_steering_rule::UpdateParams) -> Self {
        Self::from(&value)
    }
}

impl From<&po::traffic_steering_rule::Model>
    for dto::core::traffic_steering_rule::TrafficSteeringRuleItem
{
    fn from(value: &po::traffic_steering_rule::Model) -> Self {
        Self {
            traffic_steering_rule_id: value.traffic_steering_rule_id.clone(),
            inner: dto::core::traffic_steering_rule::TrafficSteeringRule {
                endpoint_id: value.endpoint_id.clone(),
                te_policy_group_id: value.te_policy_group_id.clone(),
                network_acl_id: value.network_acl_id.clone(),
                description: value.description.clone(),
            },
        }
    }
}

impl From<po::traffic_steering_rule::Model>
    for dto::core::traffic_steering_rule::TrafficSteeringRuleItem
{
    fn from(value: po::traffic_steering_rule::Model) -> Self {
        Self::from(&value)
    }
}
